// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdio.h>

#include <fbl/alloc_checker.h>
#include <fbl/unique_ptr.h>
#include <unittest/unittest.h>

#include <utility>

static int destroy_count = 0;

struct DeleteCounter {
  DeleteCounter() = default;
  DeleteCounter(int value) : value(value) {}

  static void operator delete(void* ptr) {
    destroy_count++;
    ::operator delete(ptr);
  }

  static void operator delete[](void* ptr) {
    destroy_count++;
    ::operator delete[](ptr);
  }

  int value = 0;
};

using CountingPtr = fbl::unique_ptr<DeleteCounter>;
using CountingArrPtr = fbl::unique_ptr<DeleteCounter[]>;

static_assert(std::is_standard_layout_v<int>, "fbl::unique_ptr<T>'s should have a standard layout");
static_assert(std::is_standard_layout_v<CountingPtr>,
              "fbl::unique_ptr<T>'s should have a standard layout");
static_assert(std::is_standard_layout_v<int[]>,
              "fbl::unique_ptr<T[]>'s should have a standard layout");
static_assert(std::is_standard_layout_v<CountingArrPtr>,
              "fbl::unique_ptr<T[]>'s should have a standard layout");

static bool uptr_test_scoped_destruction() {
  BEGIN_TEST;
  destroy_count = 0;

  fbl::AllocChecker ac;
  // Construct and let a unique_ptr fall out of scope.
  {
    CountingPtr ptr(new (&ac) DeleteCounter);
    EXPECT_TRUE(ac.check());
  }

  EXPECT_EQ(1, destroy_count);
  END_TEST;
}

static bool uptr_test_move() {
  BEGIN_TEST;
  destroy_count = 0;

  fbl::AllocChecker ac;
  // Construct and move into another unique_ptr.
  {
    CountingPtr ptr(new (&ac) DeleteCounter);
    EXPECT_TRUE(ac.check());

    CountingPtr ptr2 = std::move(ptr);
    EXPECT_NULL(ptr, "expected ptr to be null");
  }

  EXPECT_EQ(1, destroy_count);

  END_TEST;
}

static bool uptr_test_null_scoped_destruction() {
  BEGIN_TEST;
  destroy_count = 0;

  // Construct a null unique_ptr and let it fall out of scope - should not call
  // deleter.
  { CountingPtr ptr(nullptr); }

  EXPECT_EQ(0, destroy_count);

  END_TEST;
}

static bool uptr_test_diff_scope_swap() {
  BEGIN_TEST;
  destroy_count = 0;

  // Construct a pair of unique_ptrs in different scopes, swap them, and verify
  // that the values change places and that the values are destroyed at the
  // correct times.

  fbl::AllocChecker ac;
  {
    CountingPtr ptr1(new (&ac) DeleteCounter(4));
    EXPECT_TRUE(ac.check());
    {
      CountingPtr ptr2(new (&ac) DeleteCounter(7));
      EXPECT_TRUE(ac.check());

      ptr1.swap(ptr2);
      EXPECT_EQ(7, ptr1->value);
      EXPECT_EQ(4, ptr2->value);
    }
    EXPECT_EQ(1, destroy_count);
  }
  EXPECT_EQ(2, destroy_count);

  END_TEST;
}

static bool uptr_test_bool_op() {
  BEGIN_TEST;
  destroy_count = 0;

  fbl::AllocChecker ac;

  CountingPtr foo(new (&ac) DeleteCounter);
  EXPECT_TRUE(ac.check());
  EXPECT_TRUE(static_cast<bool>(foo));

  foo.reset();
  EXPECT_EQ(1, destroy_count);
  EXPECT_FALSE(static_cast<bool>(foo));

  END_TEST;
}

static bool uptr_test_comparison() {
  BEGIN_TEST;

  fbl::AllocChecker ac;
  // Test comparison operators.
  fbl::unique_ptr<DeleteCounter> null_unique;
  fbl::unique_ptr<DeleteCounter> lesser_unique(new (&ac) DeleteCounter(1));
  EXPECT_TRUE(ac.check());

  fbl::unique_ptr<DeleteCounter> greater_unique(new (&ac) DeleteCounter(2));
  EXPECT_TRUE(ac.check());

  EXPECT_NE(lesser_unique.get(), greater_unique.get());
  if (lesser_unique.get() > greater_unique.get())
    lesser_unique.swap(greater_unique);

  // Comparison against nullptr
  EXPECT_TRUE(null_unique == nullptr);
  EXPECT_TRUE(lesser_unique != nullptr);
  EXPECT_TRUE(greater_unique != nullptr);

  EXPECT_TRUE(nullptr == null_unique);
  EXPECT_TRUE(nullptr != lesser_unique);
  EXPECT_TRUE(nullptr != greater_unique);

  // Comparison against other unique_ptr<>s
  EXPECT_TRUE(lesser_unique == lesser_unique);
  EXPECT_FALSE(lesser_unique == greater_unique);
  EXPECT_FALSE(greater_unique == lesser_unique);
  EXPECT_TRUE(greater_unique == greater_unique);

  EXPECT_FALSE(lesser_unique != lesser_unique);
  EXPECT_TRUE(lesser_unique != greater_unique, "");
  EXPECT_TRUE(greater_unique != lesser_unique, "");
  EXPECT_FALSE(greater_unique != greater_unique);

  EXPECT_FALSE(lesser_unique < lesser_unique);
  EXPECT_TRUE(lesser_unique < greater_unique, "");
  EXPECT_FALSE(greater_unique < lesser_unique);
  EXPECT_FALSE(greater_unique < greater_unique);

  EXPECT_FALSE(lesser_unique > lesser_unique);
  EXPECT_FALSE(lesser_unique > greater_unique);
  EXPECT_TRUE(greater_unique > lesser_unique, "");
  EXPECT_FALSE(greater_unique > greater_unique);

  EXPECT_TRUE(lesser_unique <= lesser_unique, "");
  EXPECT_TRUE(lesser_unique <= greater_unique, "");
  EXPECT_FALSE(greater_unique <= lesser_unique);
  EXPECT_TRUE(greater_unique <= greater_unique, "");

  EXPECT_TRUE(lesser_unique >= lesser_unique, "");
  EXPECT_FALSE(lesser_unique >= greater_unique);
  EXPECT_TRUE(greater_unique >= lesser_unique, "");
  EXPECT_TRUE(greater_unique >= greater_unique, "");

  END_TEST;
}

static bool uptr_test_array_scoped_destruction() {
  BEGIN_TEST;
  destroy_count = 0;

  fbl::AllocChecker ac;
  // Construct and let a unique_ptr fall out of scope.
  {
    CountingArrPtr ptr(new (&ac) DeleteCounter[1]);
    EXPECT_TRUE(ac.check());
  }
  EXPECT_EQ(1, destroy_count);

  END_TEST;
}

static bool uptr_test_array_move() {
  BEGIN_TEST;
  destroy_count = 0;

  fbl::AllocChecker ac;
  // Construct and move into another unique_ptr.
  {
    CountingArrPtr ptr(new (&ac) DeleteCounter[1]);
    EXPECT_TRUE(ac.check());

    CountingArrPtr ptr2 = std::move(ptr);
    EXPECT_NULL(ptr, "expected ptr to be null");
  }
  EXPECT_EQ(1, destroy_count);

  END_TEST;
}

static bool uptr_test_array_null_scoped_destruction() {
  BEGIN_TEST;
  destroy_count = 0;

  // Construct a null unique_ptr and let it fall out of scope - should not call
  // deleter.
  { CountingArrPtr ptr(nullptr); }
  EXPECT_EQ(0, destroy_count);

  END_TEST;
}

static bool uptr_test_array_diff_scope_swap() {
  BEGIN_TEST;
  destroy_count = 0;

  // Construct a pair of unique_ptrs in different scopes, swap them, and verify
  // that the values change places and that the values are destroyed at the
  // correct times.
  fbl::AllocChecker ac;

  {
    CountingArrPtr ptr1(new (&ac) DeleteCounter[1]);
    EXPECT_TRUE(ac.check());

    ptr1[0] = 4;
    {
      CountingArrPtr ptr2(new (&ac) DeleteCounter[1]);
      EXPECT_TRUE(ac.check());

      ptr2[0] = 7;
      ptr1.swap(ptr2);
      EXPECT_EQ(7, ptr1[0].value);
      EXPECT_EQ(4, ptr2[0].value);
    }
    EXPECT_EQ(1, destroy_count);
  }
  EXPECT_EQ(2, destroy_count);

  END_TEST;
}

static bool uptr_test_array_bool_op() {
  BEGIN_TEST;
  destroy_count = 0;

  fbl::AllocChecker ac;

  CountingArrPtr foo(new (&ac) DeleteCounter[1]);
  EXPECT_TRUE(ac.check());
  EXPECT_TRUE(static_cast<bool>(foo));

  foo.reset();
  EXPECT_EQ(1, destroy_count);
  EXPECT_FALSE(static_cast<bool>(foo));

  END_TEST;
}

static bool uptr_test_array_comparison() {
  BEGIN_TEST;

  fbl::AllocChecker ac;

  fbl::unique_ptr<DeleteCounter[]> null_unique;
  fbl::unique_ptr<DeleteCounter[]> lesser_unique(new (&ac) DeleteCounter[1]);
  EXPECT_TRUE(ac.check());
  fbl::unique_ptr<DeleteCounter[]> greater_unique(new (&ac) DeleteCounter[2]);
  EXPECT_TRUE(ac.check());

  EXPECT_NE(lesser_unique.get(), greater_unique.get());
  if (lesser_unique.get() > greater_unique.get())
    lesser_unique.swap(greater_unique);

  // Comparison against nullptr
  EXPECT_TRUE(null_unique == nullptr);
  EXPECT_TRUE(lesser_unique != nullptr);
  EXPECT_TRUE(greater_unique != nullptr);

  EXPECT_TRUE(nullptr == null_unique);
  EXPECT_TRUE(nullptr != lesser_unique);
  EXPECT_TRUE(nullptr != greater_unique);

  // Comparison against other unique_ptr<>s
  EXPECT_TRUE(lesser_unique == lesser_unique);
  EXPECT_FALSE(lesser_unique == greater_unique);
  EXPECT_FALSE(greater_unique == lesser_unique);
  EXPECT_TRUE(greater_unique == greater_unique);

  EXPECT_FALSE(lesser_unique != lesser_unique);
  EXPECT_TRUE(lesser_unique != greater_unique, "");
  EXPECT_TRUE(greater_unique != lesser_unique, "");
  EXPECT_FALSE(greater_unique != greater_unique);

  EXPECT_FALSE(lesser_unique < lesser_unique);
  EXPECT_TRUE(lesser_unique < greater_unique, "");
  EXPECT_FALSE(greater_unique < lesser_unique);
  EXPECT_FALSE(greater_unique < greater_unique);

  EXPECT_FALSE(lesser_unique > lesser_unique);
  EXPECT_FALSE(lesser_unique > greater_unique);
  EXPECT_TRUE(greater_unique > lesser_unique, "");
  EXPECT_FALSE(greater_unique > greater_unique);

  EXPECT_TRUE(lesser_unique <= lesser_unique, "");
  EXPECT_TRUE(lesser_unique <= greater_unique, "");
  EXPECT_FALSE(greater_unique <= lesser_unique);
  EXPECT_TRUE(greater_unique <= greater_unique, "");

  EXPECT_TRUE(lesser_unique >= lesser_unique, "");
  EXPECT_FALSE(lesser_unique >= greater_unique);
  EXPECT_TRUE(greater_unique >= lesser_unique, "");
  EXPECT_TRUE(greater_unique >= greater_unique, "");

  END_TEST;
}

namespace upcasting {

class A {
 public:
  virtual ~A() { stuff_ = 0; }

 private:
  volatile uint32_t stuff_;
};

class B {
 public:
  ~B() { stuff_ = 1; }

 private:
  volatile uint32_t stuff_;
};

class C : public A, public B {
 public:
  ~C() { stuff_ = 2; }

 private:
  volatile uint32_t stuff_;
};

class D {
 public:
  virtual ~D() { stuff_ = 3; }

 private:
  volatile uint32_t stuff_;
};

template <typename UptrType>
static bool handoff_fn(UptrType&& ptr) {
  BEGIN_TEST;
  EXPECT_NONNULL(ptr);
  END_TEST;
}

class OverloadTestHelper {
 public:
  enum class Result {
    None,
    ClassA,
    ClassB,
    ClassD,
  };

  void PassByMove(fbl::unique_ptr<A>&&) { result_ = Result::ClassA; }
  void PassByMove(fbl::unique_ptr<D>&&) { result_ = Result::ClassD; }

#if TEST_WILL_NOT_COMPILE || 0
  // Enabling this overload should cause the overload test to fail to compile
  // due to ambiguity (it does not know whether to cast fbl::unique_ptr<C> to
  // fbl::unique_ptr<A> or fbl::unique_ptr<B>)
  void PassByMove(fbl::unique_ptr<B>&&) { result_ = Result::ClassB; }
#endif

  Result result() const { return result_; }

 private:
  Result result_ = Result::None;
};

template <typename Base, typename Derived>
static bool test_upcast() {
  BEGIN_TEST;

  fbl::AllocChecker ac;

  fbl::unique_ptr<Derived> derived_ptr;

  // Construct unique_ptr<Base> with a move and implicit cast
  derived_ptr.reset(new (&ac) Derived());
  ASSERT_TRUE(ac.check());
  {
    EXPECT_NONNULL(derived_ptr);

    fbl::unique_ptr<Base> base_ptr(std::move(derived_ptr));

    EXPECT_NULL(derived_ptr);
    EXPECT_NONNULL(base_ptr);
  }

  // Assign unique_ptr<Base> at declaration time with a std::move
  derived_ptr.reset(new (&ac) Derived());
  ASSERT_TRUE(ac.check());
  {
    EXPECT_NONNULL(derived_ptr);

    fbl::unique_ptr<Base> base_ptr = std::move(derived_ptr);

    EXPECT_NULL(derived_ptr);
    EXPECT_NONNULL(base_ptr);
  }

  // Assign unique_ptr<Base> after declaration with a std::move
  derived_ptr.reset(new (&ac) Derived());
  ASSERT_TRUE(ac.check());
  {
    fbl::unique_ptr<Base> base_ptr;
    base_ptr = std::move(derived_ptr);
  }

  // Pass the pointer to a function with a move and an implicit cast
  derived_ptr.reset(new (&ac) Derived());
  ASSERT_TRUE(ac.check());
  {
    EXPECT_NONNULL(derived_ptr);

    bool test_res = handoff_fn<fbl::unique_ptr<Base>>(std::move(derived_ptr));

    EXPECT_NULL(derived_ptr);
    EXPECT_TRUE(test_res);
  }

#if TEST_WILL_NOT_COMPILE || 0
  // Construct unique_ptr<Base> without a move.
  derived_ptr.reset(new (&ac) Derived());
  ASSERT_TRUE(ac.check());
  { fbl::unique_ptr<Base> base_ptr(derived_ptr); }
#endif

#if TEST_WILL_NOT_COMPILE || 0
  // Assign unique_ptr<Base> at declaration time without a std::move.
  derived_ptr.reset(new (&ac) Derived());
  ASSERT_TRUE(ac.check());
  { fbl::unique_ptr<Base> base_ptr = derived_ptr; }
#endif

#if TEST_WILL_NOT_COMPILE || 0
  // Assign unique_ptr<Base> after declaration without a std::move.
  derived_ptr.reset(new (&ac) Derived());
  ASSERT_TRUE(ac.check());
  {
    fbl::unique_ptr<Base> base_ptr;
    base_ptr = derived_ptr;
  }
#endif

#if TEST_WILL_NOT_COMPILE || 0
  // Pass the pointer to a function with an implicit cast but without a move.
  derived_ptr.reset(new (&ac) Derived());
  ASSERT_TRUE(ac.check());
  {
    bool test_res = handoff_fn<fbl::unique_ptr<Base>>(derived_ptr);
    EXPECT_FALSE(test_res);
  }
#endif

  END_TEST;
}

static bool uptr_upcasting() {
  BEGIN_TEST;

  bool test_res;

  // This should work.  C derives from A, A has a virtual destructor, and
  // everything is using the default deleter.
  test_res = test_upcast<A, C>();
  EXPECT_TRUE(test_res);

#if TEST_WILL_NOT_COMPILE || 0
  // This should not work.  C derives from B, but B has no virtual destructor.
  test_res = test_upcast<B, C>();
  EXPECT_FALSE(test_res);
#endif

  // This should work even though B has no virtual destructor.
  {
    fbl::AllocChecker ac;
    fbl::unique_ptr<B> ptr(new (&ac) B());
    ASSERT_TRUE(ac.check());

    fbl::unique_ptr<const B> const_ptr;
    const_ptr = std::move(ptr);

    EXPECT_NULL(ptr);
    EXPECT_NONNULL(const_ptr);
  }

#if TEST_WILL_NOT_COMPILE || 0
  // This should not work.  D has a virtual destructor, but it is not a base
  // class of C.
  test_res = test_upcast<D, C>();
  EXPECT_FALSE(test_res);
#endif

  // Test overload resolution.  Make a C and the try to pass it to
  // OverloadTestHelper's various overloaded methods.  The compiler should
  // know which version to pick, and it should pick the unique_ptr<A> version,
  // not the unique_ptr<D> version.  If the TEST_WILL_NOT_COMPILE check is
  // enabled in OverloadTestHelper, a unique_ptr<B> version will be enabled as
  // well.  This should cause the build to break because of ambiguity.
  fbl::AllocChecker ac;
  fbl::unique_ptr<C> ptr(new (&ac) C());
  ASSERT_TRUE(ac.check());

  {
    // Now test pass by move.
    OverloadTestHelper helper;
    helper.PassByMove(std::move(ptr));

    EXPECT_NULL(ptr);
    EXPECT_EQ(OverloadTestHelper::Result::ClassA, helper.result());
  }

  END_TEST;
}

}  // namespace upcasting

static bool uptr_test_make_unique() {
  BEGIN_TEST;

  // no alloc checker
  destroy_count = 0;
  {
    CountingPtr ptr = std::make_unique<DeleteCounter>(42);
    EXPECT_EQ(42, ptr->value, "value");
  }
  EXPECT_EQ(1, destroy_count);

  // with alloc checker
  destroy_count = 0;
  {
    fbl::AllocChecker ac;
    CountingPtr ptr = fbl::make_unique_checked<DeleteCounter>(&ac, 4242);
    EXPECT_TRUE(ac.check());
    EXPECT_EQ(4242, ptr->value, "value");
  }
  EXPECT_EQ(1, destroy_count);

  END_TEST;
}

static bool uptr_test_make_unique_array() {
  BEGIN_TEST;

  constexpr size_t array_size = 4;

  // no alloc checker
  destroy_count = 0;
  {
    CountingArrPtr ptr = std::make_unique<DeleteCounter[]>(array_size);
    EXPECT_NONNULL(ptr);
    for (size_t i = 0; i < array_size; ++i) {
      EXPECT_EQ(0, ptr[i].value);
    }
  }
  EXPECT_EQ(1, destroy_count);

  END_TEST;
}

BEGIN_TEST_CASE(unique_ptr)
RUN_NAMED_TEST("Scoped Destruction", uptr_test_scoped_destruction)
RUN_NAMED_TEST("Move", uptr_test_move)
RUN_NAMED_TEST("nullptr Scoped Destruction", uptr_test_null_scoped_destruction)
RUN_NAMED_TEST("Different Scope Swapping", uptr_test_diff_scope_swap)
RUN_NAMED_TEST("operator bool", uptr_test_bool_op)
RUN_NAMED_TEST("comparison operators", uptr_test_comparison)
RUN_NAMED_TEST("Array Scoped Destruction", uptr_test_array_scoped_destruction)
RUN_NAMED_TEST("Array Move", uptr_test_array_move)
RUN_NAMED_TEST("Array nullptr Scoped Destruction", uptr_test_array_null_scoped_destruction)
RUN_NAMED_TEST("Array Different Scope Swapping", uptr_test_array_diff_scope_swap)
RUN_NAMED_TEST("Array operator bool", uptr_test_array_bool_op)
RUN_NAMED_TEST("Array comparison operators", uptr_test_array_comparison)
RUN_NAMED_TEST("Upcast tests", upcasting::uptr_upcasting)
RUN_NAMED_TEST("Make unique", uptr_test_make_unique)
RUN_NAMED_TEST("Make unique array", uptr_test_make_unique_array)
END_TEST_CASE(unique_ptr)
